/**
 * Copyright (c) 2006-2015, JGraph Ltd
 * Copyright (c) 2006-2015, Gaudenz Alder
 */
var mxEdgeStyle =
    {
        /**
         * Class: mxEdgeStyle
         *
         * Provides various edge styles to be used as the values for
         * <mxConstants.STYLE_EDGE> in a cell style.
         *
         * Example:
         *
         * (code)
         * var style = stylesheet.getDefaultEdgeStyle();
         * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
         * (end)
         *
         * Sets the default edge style to <ElbowConnector>.
         *
         * Custom edge style:
         *
         * To write a custom edge style, a function must be added to the mxEdgeStyle
         * object as follows:
         *
         * (code)
         * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
         * {
         *   if (source != null && target != null)
         *   {
         *     var pt = new mxPoint(target.getCenterX(), source.getCenterY());
         *
         *     if (mxUtils.contains(source, pt.x, pt.y))
         *     {
         *       pt.y = source.y + source.height;
         *     }
         *
         *     result.push(pt);
         *   }
         * };
         * (end)
         *
         * In the above example, a right angle is created using a point on the
         * horizontal center of the target vertex and the vertical center of the source
         * vertex. The code checks if that point intersects the source vertex and makes
         * the edge straight if it does. The point is then added into the result array,
         * which acts as the return value of the function.
         *
         * The new edge style should then be registered in the <mxStyleRegistry> as follows:
         * (code)
         * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
         * (end)
         *
         * The custom edge style above can now be used in a specific edge as follows:
         *
         * (code)
         * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
         * (end)
         *
         * Note that the key of the <mxStyleRegistry> entry for the function should
         * be used in string values, unless <mxGraphView.allowEval> is true, in
         * which case you can also use mxEdgeStyle.MyStyle for the value in the
         * cell style above.
         *
         * Or it can be used for all edges in the graph as follows:
         *
         * (code)
         * var style = graph.getStylesheet().getDefaultEdgeStyle();
         * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
         * (end)
         *
         * Note that the object can be used directly when programmatically setting
         * the value, but the key in the <mxStyleRegistry> should be used when
         * setting the value via a key, value pair in a cell style.
         *
         * Function: EntityRelation
         *
         * Implements an entity relation style for edges (as used in database
         * schema diagrams). At the time the function is called, the result
         * array contains a placeholder (null) for the first absolute point,
         * that is, the point where the edge and source terminal are connected.
         * The implementation of the style then adds all intermediate waypoints
         * except for the last point, that is, the connection point between the
         * edge and the target terminal. The first ant the last point in the
         * result array are then replaced with mxPoints that take into account
         * the terminal's perimeter and next point on the edge.
         *
         * Parameters:
         *
         * state - <mxCellState> that represents the edge to be updated.
         * source - <mxCellState> that represents the source terminal.
         * target - <mxCellState> that represents the target terminal.
         * points - List of relative control points.
         * result - Array of <mxPoints> that represent the actual points of the
         * edge.
         */
        EntityRelation: function (state, source, target, points, result)
        {
            var view = state.view;
            var graph = view.graph;
            var segment = mxUtils.getValue(state.style,
                mxConstants.STYLE_SEGMENT,
                mxConstants.ENTITY_SEGMENT) * view.scale;

            var pts = state.absolutePoints;
            var p0 = pts[0];
            var pe = pts[pts.length-1];

            var isSourceLeft = false;

            if (p0 != null)
            {
                source = new mxCellState();
                source.x = p0.x;
                source.y = p0.y;
            }
            else if (source != null)
            {
                var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);

                if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
                    mxConstants.DIRECTION_MASK_EAST)
                {
                    isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
                }
                else
                {
                    var sourceGeometry = graph.getCellGeometry(source.cell);

                    if (sourceGeometry.relative)
                    {
                        isSourceLeft = sourceGeometry.x <= 0.5;
                    }
                    else if (target != null)
                    {
                        isSourceLeft = target.x + target.width < source.x;
                    }
                }
            }
            else
            {
                return;
            }

            var isTargetLeft = true;

            if (pe != null)
            {
                target = new mxCellState();
                target.x = pe.x;
                target.y = pe.y;
            }
            else if (target != null)
            {
                var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);

                if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
                    mxConstants.DIRECTION_MASK_EAST)
                {
                    isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
                }
                else
                {
                    var targetGeometry = graph.getCellGeometry(target.cell);

                    if (targetGeometry.relative)
                    {
                        isTargetLeft = targetGeometry.x <= 0.5;
                    }
                    else if (source != null)
                    {
                        isTargetLeft = source.x + source.width < target.x;
                    }
                }
            }

            if (source != null && target != null)
            {
                var x0 = (isSourceLeft) ? source.x : source.x + source.width;
                var y0 = view.getRoutingCenterY(source);

                var xe = (isTargetLeft) ? target.x : target.x + target.width;
                var ye = view.getRoutingCenterY(target);

                var seg = segment;

                var dx = (isSourceLeft) ? -seg : seg;
                var dep = new mxPoint(x0 + dx, y0);

                dx = (isTargetLeft) ? -seg : seg;
                var arr = new mxPoint(xe + dx, ye);

                // Adds intermediate points if both go out on same side
                if (isSourceLeft == isTargetLeft)
                {
                    var x = (isSourceLeft) ?
                        Math.min(x0, xe)-segment :
                        Math.max(x0, xe)+segment;

                    result.push(new mxPoint(x, y0));
                    result.push(new mxPoint(x, ye));
                }
                else if ((dep.x < arr.x) == isSourceLeft)
                {
                    var midY = y0 + (ye - y0) / 2;

                    result.push(dep);
                    result.push(new mxPoint(dep.x, midY));
                    result.push(new mxPoint(arr.x, midY));
                    result.push(arr);
                }
                else
                {
                    result.push(dep);
                    result.push(arr);
                }
            }
        },

        /**
         * Function: Loop
         *
         * Implements a self-reference, aka. loop.
         */
        Loop: function (state, source, target, points, result)
        {
            var pts = state.absolutePoints;

            var p0 = pts[0];
            var pe = pts[pts.length-1];

            if (p0 != null && pe != null)
            {
                if (points != null && points.length > 0)
                {
                    for (var i = 0; i < points.length; i++)
                    {
                        var pt = points[i];
                        pt = state.view.transformControlPoint(state, pt);
                        result.push(new mxPoint(pt.x, pt.y));
                    }
                }

                return;
            }

            if (source != null)
            {
                var view = state.view;
                var graph = view.graph;
                var pt = (points != null && points.length > 0) ? points[0] : null;

                if (pt != null)
                {
                    pt = view.transformControlPoint(state, pt);

                    if (mxUtils.contains(source, pt.x, pt.y))
                    {
                        pt = null;
                    }
                }

                var x = 0;
                var dx = 0;
                var y = 0;
                var dy = 0;

                var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
                    graph.gridSize) * view.scale;
                var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
                    mxConstants.DIRECTION_WEST);

                if (dir == mxConstants.DIRECTION_NORTH ||
                    dir == mxConstants.DIRECTION_SOUTH)
                {
                    x = view.getRoutingCenterX(source);
                    dx = seg;
                }
                else
                {
                    y = view.getRoutingCenterY(source);
                    dy = seg;
                }

                if (pt == null ||
                    pt.x < source.x ||
                    pt.x > source.x + source.width)
                {
                    if (pt != null)
                    {
                        x = pt.x;
                        dy = Math.max(Math.abs(y - pt.y), dy);
                    }
                    else
                    {
                        if (dir == mxConstants.DIRECTION_NORTH)
                        {
                            y = source.y - 2 * dx;
                        }
                        else if (dir == mxConstants.DIRECTION_SOUTH)
                        {
                            y = source.y + source.height + 2 * dx;
                        }
                        else if (dir == mxConstants.DIRECTION_EAST)
                        {
                            x = source.x - 2 * dy;
                        }
                        else
                        {
                            x = source.x + source.width + 2 * dy;
                        }
                    }
                }
                else if (pt != null)
                {
                    x = view.getRoutingCenterX(source);
                    dx = Math.max(Math.abs(x - pt.x), dy);
                    y = pt.y;
                    dy = 0;
                }

                result.push(new mxPoint(x - dx, y - dy));
                result.push(new mxPoint(x + dx, y + dy));
            }
        },

        /**
         * Function: ElbowConnector
         *
         * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
         * flag in the cell style. <SideToSide> is used if horizontal is true or
         * unspecified. See <EntityRelation> for a description of the
         * parameters.
         */
        ElbowConnector: function (state, source, target, points, result)
        {
            var pt = (points != null && points.length > 0) ? points[0] : null;

            var vertical = false;
            var horizontal = false;

            if (source != null && target != null)
            {
                if (pt != null)
                {
                    var left = Math.min(source.x, target.x);
                    var right = Math.max(source.x + source.width,
                        target.x + target.width);

                    var top = Math.min(source.y, target.y);
                    var bottom = Math.max(source.y + source.height,
                        target.y + target.height);

                    pt = state.view.transformControlPoint(state, pt);

                    vertical = pt.y < top || pt.y > bottom;
                    horizontal = pt.x < left || pt.x > right;
                }
                else
                {
                    var left = Math.max(source.x, target.x);
                    var right = Math.min(source.x + source.width,
                        target.x + target.width);

                    vertical = left == right;

                    if (!vertical)
                    {
                        var top = Math.max(source.y, target.y);
                        var bottom = Math.min(source.y + source.height,
                            target.y + target.height);

                        horizontal = top == bottom;
                    }
                }
            }

            if (!horizontal && (vertical ||
                state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))
            {
                mxEdgeStyle.TopToBottom(state, source, target, points, result);
            }
            else
            {
                mxEdgeStyle.SideToSide(state, source, target, points, result);
            }
        },

        /**
         * Function: SideToSide
         *
         * Implements a vertical elbow edge. See <EntityRelation> for a description
         * of the parameters.
         */
        SideToSide: function (state, source, target, points, result)
        {
            var view = state.view;
            var pt = (points != null && points.length > 0) ? points[0] : null;
            var pts = state.absolutePoints;
            var p0 = pts[0];
            var pe = pts[pts.length-1];

            if (pt != null)
            {
                pt = view.transformControlPoint(state, pt);
            }

            if (p0 != null)
            {
                source = new mxCellState();
                source.x = p0.x;
                source.y = p0.y;
            }

            if (pe != null)
            {
                target = new mxCellState();
                target.x = pe.x;
                target.y = pe.y;
            }

            if (source != null && target != null)
            {
                var l = Math.max(source.x, target.x);
                var r = Math.min(source.x + source.width,
                    target.x + target.width);

                var x = (pt != null) ? pt.x : Math.round(r + (l - r) / 2);

                var y1 = view.getRoutingCenterY(source);
                var y2 = view.getRoutingCenterY(target);

                if (pt != null)
                {
                    if (pt.y >= source.y && pt.y <= source.y + source.height)
                    {
                        y1 = pt.y;
                    }

                    if (pt.y >= target.y && pt.y <= target.y + target.height)
                    {
                        y2 = pt.y;
                    }
                }

                if (!mxUtils.contains(target, x, y1) &&
                    !mxUtils.contains(source, x, y1))
                {
                    result.push(new mxPoint(x,  y1));
                }

                if (!mxUtils.contains(target, x, y2) &&
                    !mxUtils.contains(source, x, y2))
                {
                    result.push(new mxPoint(x, y2));
                }

                if (result.length == 1)
                {
                    if (pt != null)
                    {
                        if (!mxUtils.contains(target, x, pt.y) &&
                            !mxUtils.contains(source, x, pt.y))
                        {
                            result.push(new mxPoint(x, pt.y));
                        }
                    }
                    else
                    {
                        var t = Math.max(source.y, target.y);
                        var b = Math.min(source.y + source.height,
                            target.y + target.height);

                        result.push(new mxPoint(x, t + (b - t) / 2));
                    }
                }
            }
        },

        /**
         * Function: TopToBottom
         *
         * Implements a horizontal elbow edge. See <EntityRelation> for a
         * description of the parameters.
         */
        TopToBottom: function(state, source, target, points, result)
        {
            var view = state.view;
            var pt = (points != null && points.length > 0) ? points[0] : null;
            var pts = state.absolutePoints;
            var p0 = pts[0];
            var pe = pts[pts.length-1];

            if (pt != null)
            {
                pt = view.transformControlPoint(state, pt);
            }

            if (p0 != null)
            {
                source = new mxCellState();
                source.x = p0.x;
                source.y = p0.y;
            }

            if (pe != null)
            {
                target = new mxCellState();
                target.x = pe.x;
                target.y = pe.y;
            }

            if (source != null && target != null)
            {
                var t = Math.max(source.y, target.y);
                var b = Math.min(source.y + source.height,
                    target.y + target.height);

                var x = view.getRoutingCenterX(source);

                if (pt != null &&
                    pt.x >= source.x &&
                    pt.x <= source.x + source.width)
                {
                    x = pt.x;
                }

                var y = (pt != null) ? pt.y : Math.round(b + (t - b) / 2);

                if (!mxUtils.contains(target, x, y) &&
                    !mxUtils.contains(source, x, y))
                {
                    result.push(new mxPoint(x, y));
                }

                if (pt != null &&
                    pt.x >= target.x &&
                    pt.x <= target.x + target.width)
                {
                    x = pt.x;
                }
                else
                {
                    x = view.getRoutingCenterX(target);
                }

                if (!mxUtils.contains(target, x, y) &&
                    !mxUtils.contains(source, x, y))
                {
                    result.push(new mxPoint(x, y));
                }

                if (result.length == 1)
                {
                    if (pt != null && result.length == 1)
                    {
                        if (!mxUtils.contains(target, pt.x, y) &&
                            !mxUtils.contains(source, pt.x, y))
                        {
                            result.push(new mxPoint(pt.x, y));
                        }
                    }
                    else
                    {
                        var l = Math.max(source.x, target.x);
                        var r = Math.min(source.x + source.width,
                            target.x + target.width);

                        result.push(new mxPoint(l + (r - l) / 2, y));
                    }
                }
            }
        },

        /**
         * Function: SegmentConnector
         *
         * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
         * as an interactive handler for this style.
         */
        SegmentConnector: function(state, source, target, hints, result)
        {
            // Creates array of all way- and terminalpoints
            var pts = state.absolutePoints;
            var tol = Math.max(1, state.view.scale);

            // Whether the first segment outgoing from the source end is horizontal
            var lastPushed = (result.length > 0) ? result[0] : null;
            var horizontal = true;
            var hint = null;

            // Adds waypoints only if outside of tolerance
            function pushPoint(pt)
            {
                if (lastPushed == null || Math.abs(lastPushed.x - pt.x) >= tol || Math.abs(lastPushed.y - pt.y) >= tol)
                {
                    result.push(pt);
                    lastPushed = pt;
                }

                return lastPushed;
            };

            // Adds the first point
            var pt = pts[0];

            if (pt == null && source != null)
            {
                pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
            }
            else if (pt != null)
            {
                pt = pt.clone();
            }

            pt.x = Math.round(pt.x);
            pt.y = Math.round(pt.y);

            var lastInx = pts.length - 1;

            // Adds the waypoints
            if (hints != null && hints.length > 0)
            {
                // Converts all hints and removes nulls
                var newHints = [];

                for (var i = 0; i < hints.length; i++)
                {
                    var tmp = state.view.transformControlPoint(state, hints[i]);

                    if (tmp != null)
                    {
                        tmp.x = Math.round(tmp.x);
                        tmp.y = Math.round(tmp.y);
                        newHints.push(tmp);
                    }
                }

                if (newHints.length == 0)
                {
                    return;
                }

                hints = newHints;

                // Aligns source and target hint to fixed points
                if (pt != null && hints[0] != null)
                {
                    if (Math.abs(hints[0].x - pt.x) < tol)
                    {
                        hints[0].x = pt.x;
                    }

                    if (Math.abs(hints[0].y - pt.y) < tol)
                    {
                        hints[0].y = pt.y;
                    }
                }

                var pe = pts[lastInx];

                if (pe != null && hints[hints.length - 1] != null)
                {
                    if (Math.abs(hints[hints.length - 1].x - pe.x) < tol)
                    {
                        hints[hints.length - 1].x = pe.x;
                    }

                    if (Math.abs(hints[hints.length - 1].y - pe.y) < tol)
                    {
                        hints[hints.length - 1].y = pe.y;
                    }
                }

                hint = hints[0];

                var currentTerm = source;
                var currentPt = pts[0];
                var hozChan = false;
                var vertChan = false;
                var currentHint = hint;

                if (currentPt != null)
                {
                    currentPt.x = Math.round(currentPt.x);
                    currentPt.y = Math.round(currentPt.y);
                    currentTerm = null;
                }

                // Check for alignment with fixed points and with channels
                // at source and target segments only
                for (var i = 0; i < 2; i++)
                {
                    var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
                    var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;

                    var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
                        currentHint.y <= currentTerm.y + currentTerm.height);
                    var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
                        currentHint.x <= currentTerm.x + currentTerm.width);

                    hozChan = fixedHozAlign || (currentPt == null && inHozChan);
                    vertChan = fixedVertAlign || (currentPt == null && inVertChan);

                    // If the current hint falls in both the hor and vert channels in the case
                    // of a floating port, or if the hint is exactly co-incident with a
                    // fixed point, ignore the source and try to work out the orientation
                    // from the target end
                    if (i==0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign)))
                    {
                    }
                    else
                    {
                        if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan))
                        {
                            horizontal = inHozChan ? false : true;
                            break;
                        }

                        if (vertChan || hozChan)
                        {
                            horizontal = hozChan;

                            if (i == 1)
                            {
                                // Work back from target end
                                horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
                            }

                            break;
                        }
                    }

                    currentTerm = target;
                    currentPt = pts[lastInx];

                    if (currentPt != null)
                    {
                        currentPt.x = Math.round(currentPt.x);
                        currentPt.y = Math.round(currentPt.y);
                        currentTerm = null;
                    }

                    currentHint = hints[hints.length - 1];

                    if (fixedVertAlign && fixedHozAlign)
                    {
                        hints = hints.slice(1);
                    }
                }

                if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
                    (pts[0] == null && source != null &&
                        (hint.y < source.y || hint.y > source.y + source.height))))
                {
                    pushPoint(new mxPoint(pt.x, hint.y));
                }
                else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
                    (pts[0] == null && source != null &&
                        (hint.x < source.x || hint.x > source.x + source.width))))
                {
                    pushPoint(new mxPoint(hint.x, pt.y));
                }

                if (horizontal)
                {
                    pt.y = hint.y;
                }
                else
                {
                    pt.x = hint.x;
                }

                for (var i = 0; i < hints.length; i++)
                {
                    horizontal = !horizontal;
                    hint = hints[i];

//				mxLog.show();
//				mxLog.debug('hint', i, hint.x, hint.y);

                    if (horizontal)
                    {
                        pt.y = hint.y;
                    }
                    else
                    {
                        pt.x = hint.x;
                    }

                    pushPoint(pt.clone());
                }
            }
            else
            {
                hint = pt;
                // FIXME: First click in connect preview toggles orientation
                horizontal = true;
            }

            // Adds the last point
            pt = pts[lastInx];

            if (pt == null && target != null)
            {
                pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
            }

            if (pt != null)
            {
                pt.x = Math.round(pt.x);
                pt.y = Math.round(pt.y);

                if (hint != null)
                {
                    if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
                        (pts[lastInx] == null && target != null &&
                            (hint.y < target.y || hint.y > target.y + target.height))))
                    {
                        pushPoint(new mxPoint(pt.x, hint.y));
                    }
                    else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
                        (pts[lastInx] == null && target != null &&
                            (hint.x < target.x || hint.x > target.x + target.width))))
                    {
                        pushPoint(new mxPoint(hint.x, pt.y));
                    }
                }
            }

            // Removes bends inside the source terminal for floating ports
            if (pts[0] == null && source != null)
            {
                while (result.length > 1 && result[1] != null &&
                mxUtils.contains(source, result[1].x, result[1].y))
                {
                    result.splice(1, 1);
                }
            }

            // Removes bends inside the target terminal
            if (pts[lastInx] == null && target != null)
            {
                while (result.length > 1 && result[result.length - 1] != null &&
                mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))
                {
                    result.splice(result.length - 1, 1);
                }
            }

            // Removes last point if inside tolerance with end point
            if (pe != null && result[result.length - 1] != null &&
                Math.abs(pe.x - result[result.length - 1].x) <= tol &&
                Math.abs(pe.y - result[result.length - 1].y) <= tol)
            {
                result.splice(result.length - 1, 1);

                // Lines up second last point in result with end point
                if (result[result.length - 1] != null)
                {
                    if (Math.abs(result[result.length - 1].x - pe.x) < tol)
                    {
                        result[result.length - 1].x = pe.x;
                    }

                    if (Math.abs(result[result.length - 1].y - pe.y) < tol)
                    {
                        result[result.length - 1].y = pe.y;
                    }
                }
            }
        },

        orthBuffer: 10,

        orthPointsFallback: true,

        dirVectors: [ [ -1, 0 ],
            [ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],

        wayPoints1: [ [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0], [ 0, 0],  [ 0, 0],
            [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0], [ 0, 0],  [ 0, 0] ],

        routePatterns: [
            [ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],
                [ 513, 1090, 514, 2564, 2184, 2562 ],
                [ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],
            [ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],
                [ 514, 2184, 2562, 1057, 513, 2564, 2184 ],
                [ 514, 1057, 513, 2568, 2308, 2561 ] ],
            [ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],
                [ 1090, 2562, 1057, 513, 2564, 2184 ],
                [ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],
            [ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],
                [ 1057, 513, 1090, 514, 2184, 2562, 2564 ],
                [ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],

        inlineRoutePatterns: [
            [ null, [ 2114, 2568 ], null, null ],
            [ null, [ 514, 2081, 2114, 2568 ] , null, null ],
            [ null, [ 2114, 2561 ], null, null ],
            [ [ 2081, 2562 ], [ 1057, 2114, 2568 ],
                [ 2184, 2562 ],
                null ] ],
        vertexSeperations: [],

        limits: [
            [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
            [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],

        LEFT_MASK: 32,

        TOP_MASK: 64,

        RIGHT_MASK: 128,

        BOTTOM_MASK: 256,

        LEFT: 1,

        TOP: 2,

        RIGHT: 4,

        BOTTOM: 8,

        // TODO remove magic numbers
        SIDE_MASK: 480,
        //mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
        //| mxEdgeStyle.BOTTOM_MASK,

        CENTER_MASK: 512,

        SOURCE_MASK: 1024,

        TARGET_MASK: 2048,

        VERTEX_MASK: 3072,
        // mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,

        getJettySize: function(state, source, target, points, isSource)
        {
            var value = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_SOURCE_JETTY_SIZE :
                mxConstants.STYLE_TARGET_JETTY_SIZE, mxUtils.getValue(state.style,
                mxConstants.STYLE_JETTY_SIZE, mxEdgeStyle.orthBuffer));

            if (value == 'auto')
            {
                // Computes the automatic jetty size
                var type = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW, mxConstants.NONE);

                if (type != mxConstants.NONE)
                {
                    var size = mxUtils.getNumber(state.style, (isSource) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
                    value = Math.max(2, Math.ceil((size + mxEdgeStyle.orthBuffer) / mxEdgeStyle.orthBuffer)) * mxEdgeStyle.orthBuffer;
                }
                else
                {
                    value = 2 * mxEdgeStyle.orthBuffer;
                }
            }

            return value;
        },

        /**
         * Function: OrthConnector
         *
         * Implements a local orthogonal router between the given
         * cells.
         *
         * Parameters:
         *
         * state - <mxCellState> that represents the edge to be updated.
         * source - <mxCellState> that represents the source terminal.
         * target - <mxCellState> that represents the target terminal.
         * points - List of relative control points.
         * result - Array of <mxPoints> that represent the actual points of the
         * edge.
         *
         */
        OrthConnector: function(state, source, target, points, result)
        {
            var graph = state.view.graph;
            var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
            var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);

            var pts = state.absolutePoints;
            var p0 = pts[0];
            var pe = pts[pts.length-1];

            var sourceX = source != null ? source.x : p0.x;
            var sourceY = source != null ? source.y : p0.y;
            var sourceWidth = source != null ? source.width : 0;
            var sourceHeight = source != null ? source.height : 0;

            var targetX = target != null ? target.x : pe.x;
            var targetY = target != null ? target.y : pe.y;
            var targetWidth = target != null ? target.width : 0;
            var targetHeight = target != null ? target.height : 0;

            var scaledSourceBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, true);
            var scaledTargetBuffer = state.view.scale * mxEdgeStyle.getJettySize(state, source, target, points, false);

            // Workaround for loop routing within buffer zone
            if (source != null && target == source)
            {
                scaledTargetBuffer = Math.max(scaledSourceBuffer, scaledTargetBuffer);
                scaledSourceBuffer = scaledTargetBuffer;
            }

            var totalBuffer = scaledTargetBuffer + scaledSourceBuffer;
            var tooShort = false;

            // Checks minimum distance for fixed points and falls back to segment connector
            if (p0 != null && pe != null)
            {
                var dx = pe.x - p0.x;
                var dy = pe.y - p0.y;

                tooShort = dx * dx + dy * dy < totalBuffer * totalBuffer;
            }

            if (tooShort || (mxEdgeStyle.orthPointsFallback && (points != null &&
                points.length > 0)) || sourceEdge || targetEdge)
            {
                mxEdgeStyle.SegmentConnector(state, source, target, points, result);

                return;
            }

            // Determine the side(s) of the source and target vertices
            // that the edge may connect to
            // portConstraint [source, target]
            var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
            var rotation = 0;

            if (source != null)
            {
                portConstraint[0] = mxUtils.getPortConstraints(source, state, true,
                    mxConstants.DIRECTION_MASK_ALL);
                rotation = mxUtils.getValue(source.style, mxConstants.STYLE_ROTATION, 0);

                if (rotation != 0)
                {
                    var newRect = mxUtils.getBoundingBox(new mxRectangle(sourceX, sourceY, sourceWidth, sourceHeight), rotation);
                    sourceX = newRect.x;
                    sourceY = newRect.y;
                    sourceWidth = newRect.width;
                    sourceHeight = newRect.height;
                }
            }

            if (target != null)
            {
                portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
                    mxConstants.DIRECTION_MASK_ALL);
                rotation = mxUtils.getValue(target.style, mxConstants.STYLE_ROTATION, 0);

                if (rotation != 0)
                {
                    var newRect = mxUtils.getBoundingBox(new mxRectangle(targetX, targetY, targetWidth, targetHeight), rotation);
                    targetX = newRect.x;
                    targetY = newRect.y;
                    targetWidth = newRect.width;
                    targetHeight = newRect.height;
                }
            }

            // Avoids floating point number errors
            sourceX = Math.round(sourceX * 10) / 10;
            sourceY = Math.round(sourceY * 10) / 10;
            sourceWidth = Math.round(sourceWidth * 10) / 10;
            sourceHeight = Math.round(sourceHeight * 10) / 10;

            targetX = Math.round(targetX * 10) / 10;
            targetY = Math.round(targetY * 10) / 10;
            targetWidth = Math.round(targetWidth * 10) / 10;
            targetHeight = Math.round(targetHeight * 10) / 10;

            var dir = [0, 0];

            // Work out which faces of the vertices present against each other
            // in a way that would allow a 3-segment connection if port constraints
            // permitted.
            // geo -> [source, target] [x, y, width, height]
            var geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,
                [targetX, targetY, targetWidth, targetHeight] ];
            var buffer = [scaledSourceBuffer, scaledTargetBuffer];

            for (var i = 0; i < 2; i++)
            {
                mxEdgeStyle.limits[i][1] = geo[i][0] - buffer[i];
                mxEdgeStyle.limits[i][2] = geo[i][1] - buffer[i];
                mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + buffer[i];
                mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + buffer[i];
            }

            // Work out which quad the target is in
            var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
            var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
            var targetCenX = geo[1][0] + geo[1][2] / 2.0;
            var targetCenY = geo[1][1] + geo[1][3] / 2.0;

            var dx = sourceCenX - targetCenX;
            var dy = sourceCenY - targetCenY;

            var quad = 0;

            if (dx < 0)
            {
                if (dy < 0)
                {
                    quad = 2;
                }
                else
                {
                    quad = 1;
                }
            }
            else
            {
                if (dy <= 0)
                {
                    quad = 3;

                    // Special case on x = 0 and negative y
                    if (dx == 0)
                    {
                        quad = 2;
                    }
                }
            }

            // Check for connection constraints
            var currentTerm = null;

            if (source != null)
            {
                currentTerm = p0;
            }

            var constraint = [ [0.5, 0.5] , [0.5, 0.5] ];

            for (var i = 0; i < 2; i++)
            {
                if (currentTerm != null)
                {
                    constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];

                    if (Math.abs(currentTerm.x - geo[i][0]) <= 1)
                    {
                        dir[i] = mxConstants.DIRECTION_MASK_WEST;
                    }
                    else if (Math.abs(currentTerm.x - geo[i][0] - geo[i][2]) <= 1)
                    {
                        dir[i] = mxConstants.DIRECTION_MASK_EAST;
                    }

                    constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];

                    if (Math.abs(currentTerm.y - geo[i][1]) <= 1)
                    {
                        dir[i] = mxConstants.DIRECTION_MASK_NORTH;
                    }
                    else if (Math.abs(currentTerm.y - geo[i][1] - geo[i][3]) <= 1)
                    {
                        dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
                    }
                }

                currentTerm = null;

                if (target != null)
                {
                    currentTerm = pe;
                }
            }

            var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
            var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
            var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
            var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);

            mxEdgeStyle.vertexSeperations[1] = Math.max(sourceLeftDist - totalBuffer, 0);
            mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - totalBuffer, 0);
            mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - totalBuffer, 0);
            mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - totalBuffer, 0);

            //==============================================================
            // Start of source and target direction determination

            // Work through the preferred orientations by relative positioning
            // of the vertices and list them in preferred and available order

            var dirPref = [];
            var horPref = [];
            var vertPref = [];

            horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
                : mxConstants.DIRECTION_MASK_EAST;
            vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
                : mxConstants.DIRECTION_MASK_SOUTH;

            horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
            vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);

            var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
                : sourceRightDist;
            var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
                : sourceBottomDist;

            var prefOrdering = [ [0, 0] , [0, 0] ];
            var preferredOrderSet = false;

            // If the preferred port isn't available, switch it
            for (var i = 0; i < 2; i++)
            {
                if (dir[i] != 0x0)
                {
                    continue;
                }

                if ((horPref[i] & portConstraint[i]) == 0)
                {
                    horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
                }

                if ((vertPref[i] & portConstraint[i]) == 0)
                {
                    vertPref[i] = mxUtils
                        .reversePortConstraints(vertPref[i]);
                }

                prefOrdering[i][0] = vertPref[i];
                prefOrdering[i][1] = horPref[i];
            }

            if (preferredVertDist > 0
                && preferredHorizDist > 0)
            {
                // Possibility of two segment edge connection
                if (((horPref[0] & portConstraint[0]) > 0)
                    && ((vertPref[1] & portConstraint[1]) > 0))
                {
                    prefOrdering[0][0] = horPref[0];
                    prefOrdering[0][1] = vertPref[0];
                    prefOrdering[1][0] = vertPref[1];
                    prefOrdering[1][1] = horPref[1];
                    preferredOrderSet = true;
                }
                else if (((vertPref[0] & portConstraint[0]) > 0)
                    && ((horPref[1] & portConstraint[1]) > 0))
                {
                    prefOrdering[0][0] = vertPref[0];
                    prefOrdering[0][1] = horPref[0];
                    prefOrdering[1][0] = horPref[1];
                    prefOrdering[1][1] = vertPref[1];
                    preferredOrderSet = true;
                }
            }

            if (preferredVertDist > 0 && !preferredOrderSet)
            {
                prefOrdering[0][0] = vertPref[0];
                prefOrdering[0][1] = horPref[0];
                prefOrdering[1][0] = vertPref[1];
                prefOrdering[1][1] = horPref[1];
                preferredOrderSet = true;

            }

            if (preferredHorizDist > 0 && !preferredOrderSet)
            {
                prefOrdering[0][0] = horPref[0];
                prefOrdering[0][1] = vertPref[0];
                prefOrdering[1][0] = horPref[1];
                prefOrdering[1][1] = vertPref[1];
                preferredOrderSet = true;
            }

            // The source and target prefs are now an ordered list of
            // the preferred port selections
            // If the list contains gaps, compact it

            for (var i = 0; i < 2; i++)
            {
                if (dir[i] != 0x0)
                {
                    continue;
                }

                if ((prefOrdering[i][0] & portConstraint[i]) == 0)
                {
                    prefOrdering[i][0] = prefOrdering[i][1];
                }

                dirPref[i] = prefOrdering[i][0] & portConstraint[i];
                dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
                dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
                dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;

                if ((dirPref[i] & 0xF) == 0)
                {
                    dirPref[i] = dirPref[i] << 8;
                }

                if ((dirPref[i] & 0xF00) == 0)
                {
                    dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
                }

                if ((dirPref[i] & 0xF0000) == 0)
                {
                    dirPref[i] = (dirPref[i] & 0xFFFF)
                        | ((dirPref[i] & 0xF000000) >> 8);
                }

                dir[i] = dirPref[i] & 0xF;

                if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST
                    || portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH
                    || portConstraint[i] == mxConstants.DIRECTION_MASK_EAST
                    || portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)
                {
                    dir[i] = portConstraint[i];
                }
            }

            //==============================================================
            // End of source and target direction determination

            var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
                : dir[0];
            var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
                : dir[1];

            sourceIndex -= quad;
            targetIndex -= quad;

            if (sourceIndex < 1)
            {
                sourceIndex += 4;
            }

            if (targetIndex < 1)
            {
                targetIndex += 4;
            }

            var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];

            mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
            mxEdgeStyle.wayPoints1[0][1] = geo[0][1];

            switch (dir[0])
            {
                case mxConstants.DIRECTION_MASK_WEST:
                    mxEdgeStyle.wayPoints1[0][0] -= scaledSourceBuffer;
                    mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
                    break;
                case mxConstants.DIRECTION_MASK_SOUTH:
                    mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
                    mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + scaledSourceBuffer;
                    break;
                case mxConstants.DIRECTION_MASK_EAST:
                    mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledSourceBuffer;
                    mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
                    break;
                case mxConstants.DIRECTION_MASK_NORTH:
                    mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
                    mxEdgeStyle.wayPoints1[0][1] -= scaledSourceBuffer;
                    break;
            }

            var currentIndex = 0;

            // Orientation, 0 horizontal, 1 vertical
            var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
                : 1;
            var initialOrientation = lastOrientation;
            var currentOrientation = 0;

            for (var i = 0; i < routePattern.length; i++)
            {
                var nextDirection = routePattern[i] & 0xF;

                // Rotate the index of this direction by the quad
                // to get the real direction
                var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
                    : nextDirection;

                directionIndex += quad;

                if (directionIndex > 4)
                {
                    directionIndex -= 4;
                }

                var direction = mxEdgeStyle.dirVectors[directionIndex - 1];

                currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
                // Only update the current index if the point moved
                // in the direction of the current segment move,
                // otherwise the same point is moved until there is
                // a segment direction change
                if (currentOrientation != lastOrientation)
                {
                    currentIndex++;
                    // Copy the previous way point into the new one
                    // We can't base the new position on index - 1
                    // because sometime elbows turn out not to exist,
                    // then we'd have to rewind.
                    mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
                    mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
                }

                var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
                var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
                var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
                side = side << quad;

                if (side > 0xF)
                {
                    side = side >> 4;
                }

                var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;

                if ((sou || tar) && side < 9)
                {
                    var limit = 0;
                    var souTar = sou ? 0 : 1;

                    if (center && currentOrientation == 0)
                    {
                        limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
                    }
                    else if (center)
                    {
                        limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
                    }
                    else
                    {
                        limit = mxEdgeStyle.limits[souTar][side];
                    }

                    if (currentOrientation == 0)
                    {
                        var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
                        var deltaX = (limit - lastX) * direction[0];

                        if (deltaX > 0)
                        {
                            mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
                                * deltaX;
                        }
                    }
                    else
                    {
                        var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
                        var deltaY = (limit - lastY) * direction[1];

                        if (deltaY > 0)
                        {
                            mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
                                * deltaY;
                        }
                    }
                }

                else if (center)
                {
                    // Which center we're travelling to depend on the current direction
                    mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
                        * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
                    mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
                        * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
                }

                if (currentIndex > 0
                    && mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])
                {
                    currentIndex--;
                }
                else
                {
                    lastOrientation = currentOrientation;
                }
            }

            for (var i = 0; i <= currentIndex; i++)
            {
                if (i == currentIndex)
                {
                    // Last point can cause last segment to be in
                    // same direction as jetty/approach. If so,
                    // check the number of points is consistent
                    // with the relative orientation of source and target
                    // jx. Same orientation requires an even
                    // number of turns (points), different requires
                    // odd.
                    var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
                        : 1;
                    var sameOrient = targetOrientation == initialOrientation ? 0 : 1;

                    // (currentIndex + 1) % 2 is 0 for even number of points,
                    // 1 for odd
                    if (sameOrient != (currentIndex + 1) % 2)
                    {
                        // The last point isn't required
                        break;
                    }
                }

                result.push(new mxPoint(Math.round(mxEdgeStyle.wayPoints1[i][0]), Math.round(mxEdgeStyle.wayPoints1[i][1])));
            }

            // Removes duplicates
            var index = 1;

            while (index < result.length)
            {
                if (result[index - 1] == null || result[index] == null ||
                    result[index - 1].x != result[index].x ||
                    result[index - 1].y != result[index].y)
                {
                    index++;
                }
                else
                {
                    result.splice(index, 1);
                }
            }
        },

        getRoutePattern: function(dir, quad, dx, dy)
        {
            var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
                : dir[0];
            var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
                : dir[1];

            sourceIndex -= quad;
            targetIndex -= quad;

            if (sourceIndex < 1)
            {
                sourceIndex += 4;
            }
            if (targetIndex < 1)
            {
                targetIndex += 4;
            }

            var result = routePatterns[sourceIndex - 1][targetIndex - 1];

            if (dx == 0 || dy == 0)
            {
                if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)
                {
                    result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
                }
            }

            return result;
        }
    };
